/**
 * 	 DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *   @author Colin Redmond
 *   Copyright (C) 2011 Colin Redmond
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package com.cred.industries.core.exceptions;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.core.Response.Status;

import com.cred.industries.core.transferObj.result.ReturnCodes;

/**
 * Our exception strategy is to wrap all low level exceptions
 * and bubble them up to the caller since there is nothing we can do. 
 * 
 * To prevent us from having to multiple exceptions on the way up
 * we wrap them with this and throw this up
 * 
 * It allows you to add a message to an exception and each time you catch and re-throw it
 */
public class ApplicationException extends RuntimeException {
	
	private static final long serialVersionUID = 165445543738984470L;
	
	protected List<ExceptionInfo> mExcpetionInfo = new ArrayList<>();
	
	private Status mStatusCode = Status.INTERNAL_SERVER_ERROR;

	public Status getStatusCode() { return mStatusCode; }
 
	/**
	 * internal class used to hold all the exception information. Can be added to as 
	 * we throw back up the stack
	 * @author Colin
	 *
	 */
	protected class ExceptionInfo {
		
		private String mErrorClass;
		private String mErrorMethod;
		private String mErrorCondition;
		private String mErrorMsg;
		
		/**
		 * contructor
		 * @param eclass class throwing the exception
		 * @param method method throwing the exception
		 * @param errorCondition specific error code or reason
		 * @param msg message in human readable form
		 */
		public ExceptionInfo(String eclass, String method, String errorCondition, String msg) {

			this.mErrorClass = eclass;
			this.mErrorMethod = method;
			this.mErrorCondition   = errorCondition;
			this.mErrorMsg   = msg;
		 }
		
		public String getErrorClass() {
			return mErrorClass;
		}

		public String getErrorMethod() {
			return mErrorMethod;
		}

		public String getErrorCondition() {
			return mErrorCondition;
		}

		public String getErrorMsg() {
			return mErrorMsg;
		}
	}

	/**
	 * Constructor 
	 * @param e the exception thrown
	 * @param errorCode specific error code or reason
	 * @param errorMsg message in human readable form
	 */
	public ApplicationException(Throwable e, String errorCode, String errorMsg) {
		
		super(e);
		mExcpetionInfo.add(new ExceptionInfo(Thread.currentThread().getStackTrace()[2].getClassName(),
				Thread.currentThread().getStackTrace()[2].getMethodName(),
				errorCode, errorMsg));
	}
	
	/**
	 * Constructor 
	 * @param e the exception thrown
	 * @param errorCode specific error code or reason
	 * @param errorMsg message in human readable form
	 */
	public ApplicationException(Throwable e, ReturnCodes errorCode, String errorMsg) {
		
		super(e);
		mExcpetionInfo.add(new ExceptionInfo(Thread.currentThread().getStackTrace()[2].getClassName(),
				Thread.currentThread().getStackTrace()[2].getMethodName(),
				errorCode.toString(), errorMsg));
	}
	
	/**
	 * Constructor
	 * @param errorCode specific error code or reason
	 * @param errorMsg message in human readable form
	 */
	public ApplicationException(String errorCode, String errorMsg) {
		
		mExcpetionInfo.add(new ExceptionInfo(Thread.currentThread().getStackTrace()[2].getClassName(),
				Thread.currentThread().getStackTrace()[2].getMethodName(), 
				errorCode, errorMsg));
	}
	
	/**
	 * Constructor
	 * @param errorCode specific error code or reason
	 * @param errorMsg message in human readable form
	 */
	public ApplicationException(ReturnCodes errorCode, String errorMsg) {
		
		mExcpetionInfo.add(new ExceptionInfo(Thread.currentThread().getStackTrace()[2].getClassName(),
				Thread.currentThread().getStackTrace()[2].getMethodName(), 
				errorCode.toString(), errorMsg));
	}
	
	/**
	 * Constructor
	 * @param statusCode the Http status code that we want to return to the caller
	 * @param errorCode specific error code or reason
	 * @param errorMsg message in human readable form
	 */
	public ApplicationException(Status statusCode, String errorCode, String errorMsg) {
		
		mStatusCode = statusCode;
		mExcpetionInfo.add(new ExceptionInfo(Thread.currentThread().getStackTrace()[2].getClassName(),
				Thread.currentThread().getStackTrace()[2].getMethodName(), 
				errorCode, errorMsg));
	}
	
	/**
	 * Constructor
	 * @param statusCode the Http status code that we want to return to the caller
	 * @param errorCode specific error code or reason
	 * @param errorMsg message in human readable form
	 */
	public ApplicationException(Status statusCode, ReturnCodes errorCode, String errorMsg) {
		
		mStatusCode = statusCode;
		mExcpetionInfo.add(new ExceptionInfo(Thread.currentThread().getStackTrace()[2].getClassName(),
				Thread.currentThread().getStackTrace()[2].getMethodName(), 
				errorCode.toString(), errorMsg));
	}
	
	/**
	 * Can be used to add more information to the exception when caught and re thrown.
	 * It will automatically add the class name and method information to the added info
	 * @param errorCode specific error code or reason
	 * @param errorMsg message in human readable form
	 * @return
	 */
	public ApplicationException addExceptionInfo(String errorCode, String errorMsg) {
		
		mExcpetionInfo.add(new ExceptionInfo(Thread.currentThread().getStackTrace()[2].getClassName(),
				Thread.currentThread().getStackTrace()[2].getMethodName(),
				errorCode, errorMsg));
		
		return this;
	}
	
	/**
	 * Can be used to add more information to the exception when caught and re thrown.
	 * It will automatically add the class name and method information to the added info
	 * @param errorCode specific error code or reason
	 * @param errorMsg message in human readable form
	 * @return
	 */
	public ApplicationException addExceptionInfo(ReturnCodes errorCode, String errorMsg) {
		
		mExcpetionInfo.add(new ExceptionInfo(Thread.currentThread().getStackTrace()[2].getClassName(),
				Thread.currentThread().getStackTrace()[2].getMethodName(),
				errorCode.toString(), errorMsg));
		
		return this;
	}
	
	/**
	 * generates a string to represent the exception
	 * @return the string version with all the details about the exception
	 */
	@Override
	 public String toString() {
		 
		 StringBuilder builder = new StringBuilder();
		
		 for(int i = this.mExcpetionInfo.size() - 1; i >= 0; i--) {
				
			ExceptionInfo exInfo = this.mExcpetionInfo.get(i);
			builder.append(exInfo.mErrorClass);
			builder.append(".");
			builder.append(exInfo.mErrorMethod);
			builder.append(": ");
			builder.append(exInfo.mErrorCondition);
			builder.append(", ");
			builder.append(exInfo.mErrorMsg);
			builder.append('\n');
		}

		//append original exception and add it to the string
		if(super.getMessage() != null) {
			if(super.getCause() == null) {
				builder.append(super.getMessage());
			} else if(!getMessage().equals(super.getCause().toString())) {
				builder.append(super.getMessage());
			}
		}
	    appendException(builder, getCause());

	    return builder.toString();
	}
	
	/**
	 * 
	 * @return the error condition that caused this exception. 
	 *    They can be in Return Codes, but dont need to be.
	 */
	public String getErrorCondition() {
		
		if(this.mExcpetionInfo.size() > 0) {
			return this.mExcpetionInfo.get(0).mErrorCondition;
		} else {
			return super.getMessage();
		}
	}

	/**
	 * allows you to append more information to the exception as it 
	 * makes its way of the stack
	 * @param builder more information about the exception to append
	 * @param throwable the exception
	 */
	private void appendException(StringBuilder builder, Throwable throwable) {
		
		if(throwable == null) return;
		appendException(builder, throwable.getCause());
		builder.append(throwable.toString());
	}
	
	/**
	 * allows you to append more information to the exception as it 
	 * makes its way of the stack
	 * @param errorCode a code to represent the error
	 * @param errorMsg detailed description for the error
	 */
	public void appendException(String errorCode, String errorMsg) {
		
		mExcpetionInfo.add(new ExceptionInfo(Thread.currentThread().getStackTrace()[2].getClassName(),
				Thread.currentThread().getStackTrace()[2].getMethodName(), 
				errorCode, errorMsg));
	}
}
